/**
 * 
 */
package Verifier.FiatShamirProofsVerification;

import Verifier.ArithmeticSystem.ArithmeticConvertor;
import Verifier.ArithmeticSystem.FieldElement;
import Verifier.ArithmeticSystem.GroupElement;
import Verifier.ArithmeticSystem.ProductElement;
import Verifier.ByteTree.ByteTree;
import Verifier.ByteTree.Leaf;
import Verifier.ByteTree.Node;

/**
 * The class executes Algorithm No.17 to verify the proof of Shuffle Of Ciphertexts Consistent Commitment.
 * 
 * @author Moshe Baavur
 *
 */
public class ShuffleOfCiphertextsConsistentCommitmentProofVerifier extends ProofVerifier {
	GroupElement elementATag;
	ProductElement elementBTag;
	
	FieldElement elementK_A;
	FieldElement elementK_B;
	FieldElement[] elementK_E;
	
	GroupElement elementA;
	ProductElement elementB;
	
	/**
	 * Initializes the proof with the correct information.
	 * 
	 * @param info the proof info which has all the needed arguments initialized using the second init method.
	 */
	public ShuffleOfCiphertextsConsistentCommitmentProofVerifier(FiatShamirProofInfo info) {
		super(info);
		
		this.elementB = null;
		this.elementATag = null;
		this.elementBTag = null;
		
		this.elementK_A = null;
		this.elementK_B = null;
		this.elementK_E = null;
		
		this.elementA = null;
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#checkInputValidity()
	 */
	@Override
	protected boolean checkInputValidity() {
		// no checking is needed at the moment - the input parameters are controlled by us
		return (true);
	}
	
	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretCommitment()
	 */
	@Override
	protected boolean interpretCommitment() {
		// interpret the commitment
		ByteTree commitmentTree = new ByteTree(proofInfo.proofCommitment());

		if (commitmentTree.getFirstByteTreeElement() instanceof Leaf) {
			return (false);
		}

		Node commitmentRoot = (Node) commitmentTree.getFirstByteTreeElement();
		// the number of children should be 2 representing: A', B'
		if (2 != commitmentRoot.getDataLength()) {
			return (false);
		}
		
		this.elementATag = null;
		this.elementBTag = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			elementATag = ArithmeticConvertor.ToGroupElement(
					new ByteTree(commitmentRoot.getChild(0)),
					proofInfo.worldGroup());
			elementBTag = ArithmeticConvertor.ToProductElement(
					new ByteTree(commitmentRoot.getChild(1)),
					proofInfo.ciphertextsGroup());
		} catch (Exception e) {
			return (false);
		}
		
		// check that the product contains exactly two elements
		if (2 != elementBTag.getNumberOfElements()) {
			return (false);
		}
		
		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#interpretReply()
	 */
	@Override
	protected boolean interpretReply() {
		// interpret the reply
		ByteTree replyTree = new ByteTree(proofInfo.proofReply());

		if (replyTree.getFirstByteTreeElement() instanceof Leaf) {
			return (false);
		}

		Node replyRoot = (Node) replyTree.getFirstByteTreeElement();
		// the number of children should be 3 representing: k_A, k_B, k_E
		if (3 != replyRoot.getDataLength()) {
			return (false);
		}

		elementK_A = null;
		elementK_B = null;
		elementK_E = null;

		try {
			// the checks of them about being in the group is made in the
			// creation process
			elementK_A = ArithmeticConvertor.ToFieldElement(new ByteTree(
					replyRoot.getChild(0)), proofInfo.worldField());
			elementK_B = ArithmeticConvertor.ToFieldElement(new ByteTree(
					replyRoot.getChild(1)), proofInfo.randomizerGroup());
			elementK_E = ArithmeticConvertor.ToFieldElementArray(new ByteTree(
					replyRoot.getChild(2)), proofInfo.worldField());
		} catch (Exception e) {
			return (false);
		}

		// now, check that the array is in the appropriate size
		if (proofInfo.arraysSize() != elementK_E.length) {
			return (false);
		}

		// all interpreted well
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#computeSeed()
	 */
	@Override
	protected void computeSeed() throws Exception {
		// compute a seed s using the RandomOracle
		Node seedGroupNode = new Node(null);

		try {
			ByteTree creatorByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.worldGroup().getGroupCreatorAsGroupElement());
			ByteTree generatorsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.independentGenerators());
			ByteTree pedersenByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.pedersenCommitments());
			
			// the public key
			ByteTree publicKeyByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.elGamalPublicKey());
			// noted as w
			ByteTree inputCiphertextsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.inputCiphertext());
			// noted as w'
			ByteTree outputCiphertextsByteTree = ArithmeticConvertor.ToByteTree(proofInfo
					.outputCiphertext());
	
			seedGroupNode.addElement(creatorByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(generatorsByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(pedersenByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(publicKeyByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(inputCiphertextsByteTree.getFirstByteTreeElement());
			seedGroupNode.addElement(outputCiphertextsByteTree.getFirstByteTreeElement());
		} catch (Exception e) {
			return;
		}
		// create the seed from the byte tree
		createSeedFromByteTree(new ByteTree(seedGroupNode));
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#createRandomData()
	 */
	@Override
	protected void createRandomData() {
		this.elementA = (GroupElement) calculateRandomNumber(proofInfo.pedersenCommitments());
		this.elementB = (ProductElement) calculateRandomNumber(proofInfo.inputCiphertext());
	}

	/* (non-Javadoc)
	 * @see Verifier.FiatShamirProofsVerification.ProofVerifier#validateCalculations()
	 */
	@Override
	protected boolean validateCalculations() {
		// save the group's creator - remember that this is the creator of the group - G_q
		GroupElement groupCreator = proofInfo.worldGroup().getGroupCreatorAsGroupElement();
		
		// try validating A related calculations
		try {
			// calculate h^k_e multiplication
			GroupElement elementMultiH = (GroupElement) groupCreator.power(elementK_A.getElement());
			for (int i = 0; i < proofInfo.arraysSize(); i++) {
				elementMultiH = (GroupElement) elementMultiH.multiply(proofInfo.independentGenerators()[i].power(elementK_E[i].getElement()));
			}
			if (!elementA.power(lastComputedChallenge).multiply(elementATag).equals(elementMultiH)) {
				return (false);
			}
		}catch (Exception e) {
			// couldn't validate A - something went wrong
			return (false);
		}
		
		// try validating B related calculations
		try {
			// now, encrypt the one element in the group G_q using the elGamal public key
			ProductElement elementMultiWTag = (ProductElement)proofInfo.elGamalPublicKey().power(elementK_B.getElement().negate());
			for (int i = 0; i < proofInfo.arraysSize(); i++) {
				elementMultiWTag = (ProductElement) elementMultiWTag.multiply(proofInfo.outputCiphertext()[i].power(elementK_E[i].getElement()));
			}
			if (!elementB.power(lastComputedChallenge).multiply(elementBTag).equals(elementMultiWTag)) {
				return (false);
			}
		}catch (Exception e) {
			// couldn't validate B - something went wrong
			return (false);
		}
		
		// the proof was validated successfully
		return (true);
	}
}
